#!/usr/bin/env python3
"""
QQ语音文件处理工具 - Windows优化增强版
针对Windows系统特性优化性能，提升文件捕获效率和稳定性
"""
import os
import time
import psutil
import subprocess
import logging
import platform
from pathlib import Path
from datetime import datetime
from typing import Set, Optional, List, Dict
import re
import shutil
import argparse
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import tempfile
import hashlib
import json
from tqdm import tqdm
import sys
import ctypes
from ctypes import wintypes
import msvcrt

# 确保Windows命令行支持UTF-8输出
try:
    ctypes.windll.kernel32.SetConsoleOutputCP(65001)
    ctypes.windll.kernel32.SetConsoleCP(65001)
except:
    pass

# 简化日志配置
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('qq_audio_tool.log', encoding='utf-8')
    ]
)

class Config:
    """配置参数类 - Windows优化版"""
    DEFAULT_OUTPUT_ROOT = Path.home() / "Documents/lesson/output"
    # Windows下使用更多核心提升转换速度
    MAX_WORKERS = max(1, (os.cpu_count() or 1))
    SILK_DIR = Path(__file__).parent / "silk"
    # 使用Windows临时目录作为缓存
    CACHE_DIR = Path(os.environ.get("TEMP", "~/.cache/qq_voice_converter")).expanduser()
    # 延长哈希缓存时间，减少IO操作
    HASH_CACHE_EXPIRY = 86400 * 3  # 3天
    
    # 文件捕获配置
    CAPTURE_CHECK_INTERVAL = 0.3
    PROCESS_CHECK_INTERVAL = 5
    MAX_CAPTURE_DELAY = 1.0
    
    def __init__(self):
        self.DEFAULT_OUTPUT_ROOT.mkdir(exist_ok=True, parents=True)
        self.CACHE_DIR.mkdir(exist_ok=True, parents=True)

config = Config()

class KeyboardListener:
    """Windows键盘监听器 - 优化版"""
    
    def __init__(self):
        self._running = False
        self._thread = None
        self._current_key = None
        self._msvcrt = msvcrt
        
    def _listen_loop(self):
        """监听循环 - 使用msvcrt实现高效非阻塞监听"""
        while self._running:
            if self._msvcrt.kbhit():
                key = self._msvcrt.getch().decode('utf-8', errors='ignore').lower()
                self._current_key = key
            time.sleep(0.05)  # 减少CPU占用
    
    def start(self):
        """开始监听"""
        if self._running:
            return
        self._running = True
        self._current_key = None
        self._thread = threading.Thread(target=self._listen_loop, daemon=True)
        self._thread.start()
    
    def stop(self):
        """停止监听"""
        self._running = False
        if self._thread:
            self._thread.join(timeout=1.0)
    
    def get_key(self):
        """获取当前按下的键"""
        key = self._current_key
        self._current_key = None
        return key
    
    def has_key(self, key_char):
        """检查是否按下了指定键"""
        return self._current_key == key_char.lower()

class QQVoiceConverter:
    """QQ语音转换器 - Windows性能优化版"""
    
    def __init__(self):
        self._converted_files_lock = threading.Lock()
        self.converted_files: List[Path] = []
        self.decoder_path: Optional[Path] = None
        self._hash_cache: Dict[str, tuple] = self._load_hash_cache()
        self._files_to_delete: Set[Path] = set()
        self._hash_cache_lock = threading.Lock()
        self._precompiled_decoders = {
            "64bit": config.SILK_DIR / "decoder_x64.exe",
            "32bit": config.SILK_DIR / "decoder_x86.exe"
        }

    def _load_hash_cache(self) -> Dict[str, tuple]:
        """加载文件哈希缓存"""
        cache_file = config.CACHE_DIR / "file_hashes.json"
        if cache_file.exists():
            try:
                with open(cache_file, "r", encoding="utf-8") as f:
                    cache_data = json.load(f)
                    current_time = time.time()
                    valid_cache = {}
                    for path, (hash_val, timestamp) in cache_data.items():
                        if current_time - timestamp < config.HASH_CACHE_EXPIRY:
                            valid_cache[path] = (hash_val, timestamp)
                    return valid_cache
            except Exception as e:
                logging.warning(f"加载哈希缓存失败: {e}")
        return {}

    def _save_hash_cache(self) -> None:
        """保存文件哈希缓存 - 减少写入频率"""
        try:
            with self._hash_cache_lock:
                cache_file = config.CACHE_DIR / "file_hashes.json"
                # 只在有变更时才写入
                if len(self._hash_cache) > 0:
                    with open(cache_file, "w", encoding="utf-8") as f:
                        json.dump(self._hash_cache, f, ensure_ascii=False, indent=2)
        except Exception as e:
            logging.warning(f"保存哈希缓存失败: {e}")

    def _get_file_hash(self, file_path: Path) -> str:
        """计算文件哈希值 - 优化Windows下的性能"""
        try:
            # 先检查文件是否存在且可访问
            if not file_path.exists() or not file_path.is_file():
                return ""
                
            file_stat = file_path.stat()
            file_size = file_stat.st_size
            file_mtime = file_stat.st_mtime
            
            # 使用文件大小和修改时间作为元数据快速判断
            meta_key = f"{file_size}_{file_mtime:.4f}"
            
            cache_key = str(file_path.resolve())
            with self._hash_cache_lock:
                if cache_key in self._hash_cache:
                    cached_hash, cached_time = self._hash_cache[cache_key]
                    if time.time() - cached_time < config.HASH_CACHE_EXPIRY:
                        return cached_hash
            
            # 小文件使用快速哈希，大文件分块处理
            hash_obj = hashlib.md5()
            chunk_size = 8192 if file_size < 1024 * 1024 else 65536
            
            with open(file_path, "rb") as f:
                while chunk := f.read(chunk_size):
                    hash_obj.update(chunk)
                    # 大文件处理时短暂休眠，避免占用过多IO
                    if file_size > 10 * 1024 * 1024:
                        time.sleep(0.001)
            
            file_hash = hash_obj.hexdigest()
            
            with self._hash_cache_lock:
                self._hash_cache[cache_key] = (file_hash, time.time())
            
            return file_hash
        except (FileNotFoundError, PermissionError) as e:
            logging.warning(f"无法计算文件哈希 {file_path}: {e}")
            return ""

    def compile_decoder(self) -> Path:
        """编译Silk解码器 - Windows优化版，优先使用预编译版本"""
        # 检查系统位数
        is_64bit = sys.maxsize > 2**32
        decoder_name = "decoder_x64.exe" if is_64bit else "decoder_x86.exe"
        decoder = config.SILK_DIR / decoder_name
        
        # 检查预编译版本
        if decoder.exists():
            return decoder
        
        # 检查通用名称的解码器
        generic_decoder = config.SILK_DIR / "decoder.exe"
        if generic_decoder.exists():
            return generic_decoder
        
        print("正在准备Silk解码器...")
        try:
            if not config.SILK_DIR.exists():
                print("Silk目录不存在，尝试克隆仓库...")
                # 确保git命令可用
                try:
                    subprocess.run(
                        ["git", "--version"],
                        capture_output=True,
                        text=True,
                        check=True
                    )
                except Exception:
                    raise RuntimeError("未找到git，请先安装git并添加到系统PATH")
                
                result = subprocess.run(
                    ["git", "clone", "https://github.com/kn007/silk-v3-decoder.git", str(config.SILK_DIR)],
                    capture_output=True,
                    text=True
                )
                if result.returncode != 0:
                    logging.error(f"克隆仓库失败: {result.stderr}")
                    raise RuntimeError("Silk解码器仓库克隆失败")
            
            # 优先尝试MSVC编译（Windows原生编译器）
            try:
                # 检查是否有Visual Studio环境
                vswhere = r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"
                if os.path.exists(vswhere):
                    result = subprocess.run(
                        [vswhere, "-latest", "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-find", "VC\\Auxiliary\\Build\\vcvarsall.bat"],
                        capture_output=True,
                        text=True
                    )
                    vcvarsall = result.stdout.strip()
                    
                    if vcvarsall:
                        # 通过批处理文件设置环境并编译
                        batch_content = f'"{vcvarsall}" {"x64" if is_64bit else "x86"} && cl *.c /Fe:{decoder_name} /O2'
                        with tempfile.NamedTemporaryFile(mode='w', suffix='.bat', delete=False) as f:
                            f.write(batch_content)
                            batch_path = f.name
                        
                        result = subprocess.run(
                            [batch_path],
                            cwd=config.SILK_DIR,
                            capture_output=True,
                            text=True,
                            shell=True
                        )
                        os.unlink(batch_path)
                        
                        if result.returncode == 0 and decoder.exists():
                            print("使用MSVC成功编译解码器")
                            return decoder
            except Exception as e:
                logging.info(f"MSVC编译尝试失败: {e}，尝试MinGW...")
            
            # 尝试使用MinGW (gcc)
            try:
                subprocess.run(
                    ["gcc", "--version"],
                    capture_output=True,
                    text=True,
                    check=True
                )
                
                result = subprocess.run(
                    ["gcc", "*.c", "-o", decoder_name, "-O2"], 
                    cwd=config.SILK_DIR, 
                    capture_output=True,
                    text=True
                )
                
                if result.returncode == 0 and decoder.exists():
                    print("使用MinGW成功编译解码器")
                    return decoder
            except Exception as e:
                logging.info(f"MinGW编译尝试失败: {e}")
            
            # 最后尝试通用编译命令
            result = subprocess.run(
                ["make"], 
                cwd=config.SILK_DIR, 
                capture_output=True,
                text=True,
                shell=True
            )
            
            if result.returncode != 0:
                logging.error(f"编译错误输出: {result.stderr}")
                raise RuntimeError("解码器编译失败")
            
            if not decoder.exists():
                possible_decoders = list(config.SILK_DIR.glob("decoder*"))
                if possible_decoders:
                    decoder = possible_decoders[0]
                else:
                    raise FileNotFoundError("编译成功但未找到解码器可执行文件")
            
            print("Silk解码器编译完成")
            return decoder
        
        except Exception as e:
            print(f"编译失败: {e}")
            raise

    @staticmethod
    def check_ffmpeg() -> bool:
        """检查ffmpeg是否安装 - 优化Windows路径检测"""
        try:
            # 先检查系统PATH
            result = subprocess.run(
                ["where", "ffmpeg"],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                shell=True
            )
            
            if result.returncode == 0 and "ffmpeg.exe" in result.stdout:
                return True
                
            # 检查常见安装路径
            common_paths = [
                r"C:\Program Files\ffmpeg\bin\ffmpeg.exe",
                r"C:\Program Files (x86)\ffmpeg\bin\ffmpeg.exe",
                Path.home() / "scoop/shims/ffmpeg.exe",
                Path.home() / "choco/bin/ffmpeg.exe"
            ]
            
            for path in common_paths:
                if os.path.exists(path):
                    # 将找到的路径添加到环境变量
                    os.environ["PATH"] += os.pathsep + os.path.dirname(path)
                    return True
                    
            return False
        except Exception:
            return False

    def _is_file_locked(self, file_path: Path) -> bool:
        """检查文件是否被锁定 - Windows特有实现"""
        if not file_path.exists():
            return False
            
        try:
            with open(file_path, 'rb') as f:
                # 尝试锁定文件的一个字节
                msvcrt.locking(f.fileno(), msvcrt.LK_NBLCK, 1)
                msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, 1)
                return False
        except (IOError, OSError):
            return True

    def convert_single_file(self, file_path: Path) -> Optional[Path]:
        """转换单个音频文件 - 优化Windows文件处理"""
        try:
            if not file_path.exists():
                logging.warning(f"文件不存在，跳过: {file_path}")
                return None
            
            # 检查文件是否被锁定
            if self._is_file_locked(file_path):
                logging.warning(f"文件被锁定，稍后再试: {file_path.name}")
                return None
            
            output_dir = file_path.parent
            output_mp3 = output_dir / f"{file_path.stem}.mp3"
            
            file_hash = self._get_file_hash(file_path)
            if not file_hash:
                return None
            
            # 检查是否已转换
            if output_mp3.exists():
                cache_key = str(file_path.resolve())
                with self._hash_cache_lock:
                    if cache_key in self._hash_cache and self._hash_cache[cache_key][0] == file_hash:
                        with self._converted_files_lock:
                            self.converted_files.append(output_mp3)
                        if file_path.exists() and not self._is_file_locked(file_path):
                            self._files_to_delete.add(file_path)
                        return output_mp3
            
            # 使用Windows临时目录进行转换
            with tempfile.TemporaryDirectory(dir=os.environ.get("TEMP")) as tmp_dir:
                temp_pcm = Path(tmp_dir) / "audio.pcm"
                
                # 解码操作，设置较高优先级
                result = subprocess.run(
                    [str(self.decoder_path), str(file_path), str(temp_pcm)],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    universal_newlines=True,
                    shell=True,
                    creationflags=subprocess.CREATE_NEW_PROCESS_GROUP  # Windows特有标志
                )
                if result.returncode != 0:
                    logging.warning(f"解码失败 {file_path.name}: {result.stderr}")
                    return None
                
                if not temp_pcm.exists():
                    logging.warning(f"PCM文件未生成: {file_path.name}")
                    return None
                
                # 转换为MP3
                temp_mp3 = output_mp3.with_suffix('.temp.mp3')
                result = subprocess.run(
                    ["ffmpeg", "-y", "-hide_banner", "-loglevel", "error",
                     "-f", "s16le", "-ar", "24000", "-ac", "1",
                     "-i", str(temp_pcm), "-b:a", "32k", "-filter:a", "volume=1.5",
                     str(temp_mp3)],
                    capture_output=True,
                    text=True,
                    shell=True,
                    creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
                )
                
                if result.returncode != 0:
                    logging.warning(f"FFmpeg转换失败 {file_path.name}: {result.stderr}")
                    if temp_mp3.exists():
                        temp_mp3.unlink()
                    return None
                
                # 确保文件写入完成
                if temp_mp3.exists():
                    with open(temp_mp3, 'rb') as f:
                        os.fsync(f.fileno())
                    # 处理可能的文件锁定
                    for _ in range(3):
                        try:
                            temp_mp3.rename(output_mp3)
                            break
                        except PermissionError:
                            time.sleep(0.3)
                    else:
                        raise PermissionError("无法重命名临时文件")
                else:
                    logging.warning(f"临时MP3文件未生成: {temp_mp3}")
                    return None
            
            # 更新缓存
            cache_key = str(file_path.resolve())
            with self._hash_cache_lock:
                self._hash_cache[cache_key] = (file_hash, time.time())
            with self._converted_files_lock:
                self.converted_files.append(output_mp3)
            
            # 标记原文件待删除
            if file_path.exists() and not self._is_file_locked(file_path):
                self._files_to_delete.add(file_path)
            return output_mp3
        
        except subprocess.CalledProcessError as e:
            logging.warning(f"转换失败 {file_path.name}: {e.stderr if hasattr(e, 'stderr') else str(e)}")
        except Exception as e:
            logging.warning(f"处理 {file_path.name} 时出错: {str(e)}")
        return None

    def _delete_processed_files(self) -> int:
        """删除已成功转换的原文件 - 优化Windows文件删除逻辑"""
        deleted_count = 0
        files_to_remove = []
        
        for file_path in self._files_to_delete:
            try:
                if file_path.exists() and not self._is_file_locked(file_path):
                    # Windows有时会锁定文件，重试机制
                    for _ in range(5):  # 增加重试次数
                        try:
                            file_path.unlink()
                            deleted_count += 1
                            print(f"已删除原文件: {file_path.name}")
                            files_to_remove.append(file_path)
                            break
                        except PermissionError:
                            time.sleep(0.3)  # 缩短重试间隔
            except Exception as e:
                logging.warning(f"删除原文件失败 {file_path}: {e}")
        
        for file_path in files_to_remove:
            self._files_to_delete.discard(file_path)
        
        return deleted_count

    def process_conversion(self, target_dir: Path) -> bool:
        """处理目录下的音频文件转换 - 优化线程池配置"""
        if not self.check_ffmpeg():
            install_guide = "请从https://ffmpeg.org/下载并添加到系统PATH，或安装Choco后运行: choco install ffmpeg"
            print(f"未找到ffmpeg。{install_guide}")
            return False
        
        try:
            self.decoder_path = self.compile_decoder()
        except Exception as e:
            print(f"初始化解码器失败: {e}")
            return False
        
        audio_extensions = ['.amr', '.silk', '.slk']
        audio_files = []
        for ext in audio_extensions:
            audio_files.extend([f for f in target_dir.rglob(f"*{ext}") if f.exists() and f.is_file()])
        
        total_files = len(audio_files)
        if total_files == 0:
            print("未找到任何可转换的语音文件")
            return True
        
        print(f"找到 {total_files} 个语音文件，开始处理...")
        success_count = 0
        
        def get_file_size(file_path: Path) -> int:
            try:
                return file_path.stat().st_size
            except:
                return 0
        
        # 按文件大小排序，优化转换顺序
        audio_files.sort(key=get_file_size, reverse=True)
        
        # 根据文件总数和大小动态调整批处理大小
        if total_files <= 5:
            batch_size = total_files
        elif total_files <= 20:
            batch_size = min(5, config.MAX_WORKERS)
        else:
            batch_size = config.MAX_WORKERS
        
        print()
        with tqdm(total=total_files, 
                 bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]",
                 desc="转换进度") as pbar:
            
            for i in range(0, total_files, batch_size):
                chunk = audio_files[i:i+batch_size]
                chunk_sizes = [get_file_size(f) for f in chunk]
                avg_size = sum(chunk_sizes) / len(chunk_sizes) if chunk_sizes else 0
                
                # 根据平均文件大小调整工作线程数
                adjusted_workers = batch_size if avg_size < 10*1024*1024 else max(1, batch_size // 2)
                
                with ThreadPoolExecutor(max_workers=adjusted_workers) as executor:
                    futures = {
                        executor.submit(self.convert_single_file, f): f 
                        for f in chunk
                    }
                    for future in as_completed(futures):
                        if future.result():
                            success_count += 1
                        pbar.update(1)
        
        deleted_count = self._delete_processed_files()
        
        # 定期保存哈希缓存，避免频繁写入
        self._save_hash_cache()
        
        print()
        print(f"转换完成 | 成功: {success_count}/{total_files} 个 | 已删除原文件: {deleted_count} 个")
        return True

class QQAudioMonitor:
    """QQ语音文件监控器 - Windows性能优化版"""
    
    def __init__(self, custom_voice_path: Optional[str] = None, output_root_dir: Optional[str] = None):
        self.qq_process_names = ["QQ.exe", "QQScLauncher.exe", "TXPlatform.exe", "QQProtect.exe"]
        self.previous_playing_files = set()
        self.custom_voice_path = custom_voice_path
        self.audio_extensions = {'.slk', '.silk', '.amr', '.mp3', '.m4a', '.wav', '.aac'}
        self.copy_history: Dict[str, tuple] = {}
        self.output_root_dir = self._init_output_root_dir(output_root_dir)
        self.output_date_dir: Optional[Path] = None
        self.file_counter = 1
        self.keyboard_listener = KeyboardListener()
        self.converter = QQVoiceConverter()
        self.qq_running_cache = False
        self.last_process_check = 0
        self.process_check_interval = config.PROCESS_CHECK_INTERVAL
        self.first_detection_done = False
        self._last_status = ""
        self.last_file_check_time = 0
        self.file_check_interval = config.CAPTURE_CHECK_INTERVAL
        self.recently_added_files = set()
        self._last_capture_time = 0
        self._capture_delay = config.CAPTURE_CHECK_INTERVAL
        self._consecutive_captures = 0
        
        # Windows下QQ语音文件的常见路径模式
        self.qq_voice_patterns = [
            r".*QQ.*Audio.*",
            r".*temp.*audio.*",
            r".*voice.*",
            r".*silk.*",
            r".*amr.*",
            r".*slk.*",
            r".*Users.*AppData.*Roaming.*Tencent.*QQ.*",
            r".*Users.*Documents.*Tencent Files.*",
            r".*Program Files.*Tencent.*QQ.*"
        ]
        
        # 初始化Windows API
        self._init_winapi()

    def _init_winapi(self):
        """初始化Windows API函数用于高效文件监控"""
        self.kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
        
        # 定义所需的Windows API函数
        self.kernel32.CreateToolhelp32Snapshot.argtypes = [wintypes.DWORD, wintypes.DWORD]
        self.kernel32.CreateToolhelp32Snapshot.restype = wintypes.HANDLE
        
        self.kernel32.Process32First.argtypes = [wintypes.HANDLE, ctypes.POINTER(ctypes.Structure)]
        self.kernel32.Process32First.restype = wintypes.BOOL
        
        self.kernel32.Process32Next.argtypes = [wintypes.HANDLE, ctypes.POINTER(ctypes.Structure)]
        self.kernel32.Process32Next.restype = wintypes.BOOL

    def _init_output_root_dir(self, output_root_dir: Optional[str]) -> Path:
        """初始化输出根目录 - 优化Windows路径处理"""
        if not output_root_dir:
            return config.DEFAULT_OUTPUT_ROOT
        else:
            output_root_dir = Path(output_root_dir).expanduser()
            output_root_dir.mkdir(parents=True, exist_ok=True)
            return output_root_dir
        
    def _create_date_subdir(self, first_file_path: str) -> Path:
        """根据第一个备份文件的创建日期建立子目录"""
        try:
            first_file_stat = os.stat(first_file_path)
            first_file_date = datetime.fromtimestamp(first_file_stat.st_ctime).strftime("%Y-%m-%d")
        except (OSError, ValueError) as e:
            logging.warning(f"无法获取文件 {first_file_path} 的创建时间，使用当前日期: {e}")
            first_file_date = datetime.now().strftime("%Y-%m-%d")
        
        date_subdir = self.output_root_dir / first_file_date
        date_subdir.mkdir(exist_ok=True)
        print(f"日期子目录: {date_subdir}")
        
        self.file_counter = self._get_initial_counter(date_subdir)
        return date_subdir
    
    def _get_initial_counter(self, target_dir: Path) -> int:
        """获取目标目录的初始序号"""
        if not target_dir.exists():
            return 1
        
        seq_pattern = re.compile(r'^(\d{2})\..+')
        max_seq = 0
        
        for file in target_dir.iterdir():
            if file.is_file():
                match = seq_pattern.match(file.name)
                if match:
                    seq = int(match.group(1))
                    if seq > max_seq:
                        max_seq = seq
        
        return max_seq + 1
    
    def _generate_target_path(self, original_path: str) -> Optional[Path]:
        """生成目标文件路径"""
        if not self.output_date_dir:
            self.output_date_dir = self._create_date_subdir(original_path)
        
        ext = Path(original_path).suffix.lower()
        seq_str = f"{self.file_counter:02d}"
        target_filename = f"{seq_str}{ext}"
        target_path = self.output_date_dir / target_filename
        
        self.file_counter += 1
        return target_path
    
    def _copy_file(self, original_path: str) -> Optional[str]:
        """复制文件并记录历史 - 优化Windows文件复制"""
        if original_path in self.recently_added_files:
            return None
        
        if original_path in self.copy_history:
            return self.copy_history[original_path][0]
        
        try:
            if not os.path.exists(original_path):
                logging.warning(f"源文件不存在: {original_path}")
                return None
            
            # 检查文件是否被锁定
            if self._is_file_locked(original_path):
                logging.warning(f"文件被锁定，无法复制: {original_path}")
                return None
            
            target_path = self._generate_target_path(original_path)
            if not target_path:
                return None
            
            # 使用Windows的CopyFile API进行更高效的复制
            src = ctypes.create_unicode_buffer(original_path)
            dst = ctypes.create_unicode_buffer(str(target_path))
            
            # 尝试多次复制
            success = False
            for _ in range(3):
                if ctypes.windll.kernel32.CopyFileW(src, dst, False):
                    success = True
                    break
                time.sleep(0.3)
            
            if not success:
                raise OSError(f"复制失败，错误码: {ctypes.get_last_error()}")
            
            backup_time = datetime.now()
            self.copy_history[original_path] = (str(target_path), backup_time)
            self.recently_added_files.add(original_path)
            
            # 5秒后从最近添加列表中移除
            threading.Timer(5, lambda: self.recently_added_files.discard(original_path)).start()
            
            print(f"捕获语音: {Path(original_path).name} → {target_path.name}")
            return str(target_path)
        except Exception as e:
            print(f"复制失败: {Path(original_path).name} - {e}")
            return None
    
    def _is_file_locked(self, file_path: str) -> bool:
        """检查文件是否被锁定"""
        try:
            with open(file_path, 'rb') as f:
                msvcrt.locking(f.fileno(), msvcrt.LK_NBLCK, 1)
                msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, 1)
                return False
        except (IOError, OSError):
            return True

    def _update_order_list(self) -> None:
        """更新播放文件顺序列表 - 减少IO操作频率"""
        if not self.output_date_dir:
            return
        
        # 控制更新频率，避免频繁写入
        list_path = self.output_date_dir / "播放文件顺序列表.txt"
        if list_path.exists():
            try:
                mtime = os.path.getmtime(list_path)
                if time.time() - mtime < 5:  # 5秒内不重复更新
                    return
            except:
                pass
        
        try:
            sorted_items = sorted(
                self.copy_history.items(),
                key=lambda x: x[1][1]
            )
            
            with open(list_path, 'w', encoding='utf-8') as f:
                f.write(f"QQ语音文件备份顺序列表（{self.output_date_dir.name}）\n")
                f.write(f"更新时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write("=" * 60 + "\n")
                f.write(f"{'序号':<6} {'目标文件名':<15} {'原文件路径':<50} {'备份时间':<20} {'状态':<10}\n")
                f.write("-" * 120 + "\n")
                
                for idx, (original_path, (target_path, backup_time)) in enumerate(sorted_items, 1):
                    target_file = Path(target_path)
                    backup_time_str = backup_time.strftime('%Y-%m-%d %H:%M:%S')
                    status = "存在" if target_file.exists() else "已删除"
                    f.write(f"{idx:<6} {target_file.name:<15} {original_path:<50} {backup_time_str:<20} {status:<10}\n")
            
            # 确保文件写入磁盘
            with open(list_path, 'rb') as f:
                os.fsync(f.fileno())
        except Exception as e:
            logging.error(f"更新顺序列表失败: {e}")

    def is_qq_running(self) -> bool:
        """检查QQ是否运行 - 优化Windows进程检测"""
        now = time.time()
        if now - self.last_process_check < self.process_check_interval:
            return self.qq_running_cache
        
        self.qq_running_cache = False
        try:
            # 使用Windows API枚举进程，更高效
            h_process_snap = self.kernel32.CreateToolhelp32Snapshot(0x00000002, 0)  # TH32CS_SNAPPROCESS
            if h_process_snap == ctypes.INVALID_HANDLE_VALUE:
                raise ctypes.WinError(ctypes.get_last_error())
            
            # 定义PROCESSENTRY32结构
            class PROCESSENTRY32(ctypes.Structure):
                _fields_ = [
                    ("dwSize", wintypes.DWORD),
                    ("cntUsage", wintypes.DWORD),
                    ("th32ProcessID", wintypes.DWORD),
                    ("th32DefaultHeapID", ctypes.POINTER(wintypes.ULONG)),
                    ("th32ModuleID", wintypes.DWORD),
                    ("cntThreads", wintypes.DWORD),
                    ("th32ParentProcessID", wintypes.DWORD),
                    ("pcPriClassBase", wintypes.LONG),
                    ("dwFlags", wintypes.DWORD),
                    ("szExeFile", ctypes.c_wchar * 260)
                ]
            
            pe32 = PROCESSENTRY32()
            pe32.dwSize = ctypes.sizeof(PROCESSENTRY32)
            
            if not self.kernel32.Process32First(h_process_snap, ctypes.byref(pe32)):
                ctypes.windll.kernel32.CloseHandle(h_process_snap)
                raise ctypes.WinError(ctypes.get_last_error())
            
            # 检查进程名
            while True:
                exe_name = pe32.szExeFile.lower()
                if any(qq_name.lower() in exe_name for qq_name in self.qq_process_names):
                    self.qq_running_cache = True
                    break
                
                if not self.kernel32.Process32Next(h_process_snap, ctypes.byref(pe32)):
                    break
            
            ctypes.windll.kernel32.CloseHandle(h_process_snap)
            
        except Exception as e:
            logging.debug(f"进程检测错误: {e}，使用psutil备用方案")
            # 备用方案：使用psutil
            for proc in psutil.process_iter(['name']):
                try:
                    proc_name = proc.info['name'] or ''
                    if any(qq_name.lower() in proc_name.lower() for qq_name in self.qq_process_names):
                        self.qq_running_cache = True
                        break
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    continue
        
        self.last_process_check = now
        return self.qq_running_cache

    def _is_qq_voice_file(self, file_path: str) -> bool:
        """判断是否为QQ语音文件"""
        path_lower = file_path.lower()
        
        ext = Path(file_path).suffix.lower()
        if ext not in self.audio_extensions:
            return False
        
        for pattern in self.qq_voice_patterns:
            if re.search(pattern, path_lower, re.IGNORECASE):
                return True
        
        try:
            file_size = os.path.getsize(file_path)
            if file_size < 100 or file_size > 10 * 1024 * 1024:
                return False
        except:
            return False
        
        return True

    def get_playing_audio_files(self) -> Set[str]:
        """检测正在播放的音频文件 - Windows优化版，使用更高效的文件监控"""
        now = time.time()
        time_since_last_check = now - self.last_file_check_time
        
        if time_since_last_check < self._capture_delay:
            return set()
        
        self.last_file_check_time = now
        
        playing_files = set()
        
        try:
            # 对每个QQ进程，检查其打开的文件
            for proc in psutil.process_iter(['name', 'pid']):
                try:
                    proc_name = proc.info['name'] or ''
                    if not any(qq_name.lower() in proc_name.lower() for qq_name in self.qq_process_names):
                        continue
                    
                    # 获取进程打开的文件
                    for file in proc.open_files():
                        file_path = file.path
                        if not file_path:
                            continue
                            
                        ext = Path(file_path).suffix.lower()
                        if ext in self.audio_extensions and self._is_qq_voice_file(file_path):
                            # 检查文件是否存在且未被锁定
                            if os.path.exists(file_path) and not self._is_file_locked(file_path):
                                playing_files.add(file_path)
                except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                    continue
        
        except Exception as e:
            logging.debug(f"文件检测失败: {e}")
        
        # 动态调整检测延迟
        now = time.time()
        time_since_last_capture = now - self._last_capture_time
        
        if len(playing_files) > 0:
            self._consecutive_captures += 1
            self._last_capture_time = now
            
            if self._consecutive_captures > 2:
                self._capture_delay = max(0.1, self._capture_delay * 0.8)
        else:
            self._consecutive_captures = max(0, self._consecutive_captures - 1)
            
            if time_since_last_capture > 10:
                self._capture_delay = config.CAPTURE_CHECK_INTERVAL
        
        return playing_files

    def _convert_audio_files(self):
        """转换音频文件"""
        if not self.output_root_dir.exists():
            print("输出目录不存在，无法转换")
            return False
        
        print("开始转换音频文件...")
        success = self.converter.process_conversion(self.output_root_dir)
        if success:
            self._update_order_list()
        return success

    def start_monitoring(self, check_interval: int = 2):
        """启动监控"""
        self.keyboard_listener.start()
        
        print(f"\n操作指南:")
        print(f"  f - 立即转换音频文件")
        print(f"  q - 退出程序")
        print(f"  Ctrl+C - 强制退出")
        print(f"\n已优化鼠标点击播放语音的捕获效率")
        print(f"   检测间隔: {config.CAPTURE_CHECK_INTERVAL}秒")
        print(f"   最大延迟: {config.MAX_CAPTURE_DELAY}秒")
        print()
        
        try:
            while True:
                # 检查键盘输入
                if self.keyboard_listener.has_key('q'):
                    print(f"\n收到退出指令...")
                    break
                if self.keyboard_listener.has_key('f'):
                    print()
                    self._convert_audio_files()
                    self.keyboard_listener.get_key()
                    print()
                
                # 根据QQ运行状态调整检查间隔
                current_interval = check_interval if self.is_qq_running() else 10
                
                if self.is_qq_running():
                    playing_files = self.get_playing_audio_files()
                    
                    if not self.first_detection_done:
                        self.previous_playing_files = playing_files
                        self.first_detection_done = True
                        print("已完成首次文件检测，忽略历史文件")
                    else:
                        new_playing_files = playing_files - self.previous_playing_files
                        new_target_files = []
                        
                        if new_playing_files:
                            print()
                            print(f"捕获到 {len(new_playing_files)} 个正在播放的语音文件!")
                            
                            for file_path in new_playing_files:
                                target_path = self._copy_file(file_path)
                                if target_path:
                                    new_target_files.append(target_path)
                        
                        if new_target_files:
                            self._update_order_list()
                        
                        self.previous_playing_files = playing_files
                
                else:
                    status_msg = f"QQ未运行，等待启动... | 已备份: {len(self.copy_history)}"
                    print(f"\r{status_msg}", end="", flush=True)
                    self._last_status = status_msg
                    self.previous_playing_files = set()
                    self.first_detection_done = False
                
                if self.is_qq_running() and self.first_detection_done:
                    date_dir_info = self.output_date_dir.name if self.output_date_dir else "未生成"
                    status_msg = (f"监控中... | "
                                 f"正在播放: {len(playing_files)} | "
                                 f"已备份: {len(self.copy_history)} | "
                                 f"检测延迟: {self._capture_delay:.2f}s | "
                                 f"当前目录: {date_dir_info}")
                    print(f"\r{status_msg}", end="", flush=True)
                    self._last_status = status_msg
                
                time.sleep(current_interval)
                
        except KeyboardInterrupt:
            print(f"\n监控已停止")
        except Exception as e:
            print(f"\n监控错误: {e}")
            logging.error(f"监控错误: {e}", exc_info=True)
        finally:
            self.keyboard_listener.stop()
            if self.copy_history:
                self._update_order_list()
                if self.output_date_dir:
                    print(f"\n顺序列表已保存至: {self.output_date_dir / '播放文件顺序列表.txt'}")
                    print(f"输出目录: {self.output_date_dir}")
                    print(f"备份文件数量: {len(self.copy_history)}")

def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='QQ语音文件处理工具 - Windows优化增强版')
    parser.add_argument('--input-dir', help='指定QQ语音文件目录（可选）')
    parser.add_argument('--output-root', help=f'指定输出根目录（可选，默认：{config.DEFAULT_OUTPUT_ROOT}）')
    parser.add_argument('--interval', type=int, default=2, help='检查间隔（秒，默认：2）')
    parser.add_argument('--convert-only', action='store_true', help='仅执行转换，不监控')
    parser.add_argument('--convert-dir', help=f'指定转换目录（用于仅转换模式，默认：{config.DEFAULT_OUTPUT_ROOT}）')
    args = parser.parse_args()
    
    print("QQ语音文件处理工具 - Windows优化增强版")
    
    # 检查依赖
    try:
        import psutil
    except ImportError:
        print("缺少依赖: psutil")
        print("请运行: pip install psutil")
        return
    try:
        from tqdm import tqdm
    except ImportError:
        print("缺少依赖: tqdm")
        print("请运行: pip install tqdm")
        return
    
    # 仅转换模式
    if args.convert_only:
        converter = QQVoiceConverter()
        convert_dir = Path(args.convert_dir or args.output_root or config.DEFAULT_OUTPUT_ROOT).expanduser()
        if not convert_dir.exists():
            print(f"转换目录不存在: {convert_dir}")
            return
        print(f"开始转换目录: {convert_dir}")
        success = converter.process_conversion(convert_dir)
        if success:
            print("转换完成!")
        else:
            print("转换失败!")
        return
    
    # 监控模式
    monitor = QQAudioMonitor(
        custom_voice_path=args.input_dir,
        output_root_dir=args.output_root
    )
    
    print(f"输出目录: {monitor.output_root_dir}")
    try:
        monitor.start_monitoring(check_interval=args.interval)
    except Exception as e:
        print(f"程序错误: {e}")

if __name__ == "__main__":
    main()
